﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Web;
using Kugar.Core.BaseStruct;
using Kugar.Core.ExtMethod;
using Kugar.Core.IO;
using Kugar.Core.Network.Tcp;
using System.Threading;


namespace Kugar.Core.Communications.FileTransfer
{
    public class FileSendTask
    {
        private TcpClientEx tcpClient;
        private int MainSessionID;
        private bool IsEnabledTransfer = false;
        private int offlineCount = 0;
        private TimerEx lineCheckerTimer = null;
        private bool isEnd = false;

        private FileSendTask()
        {
            lineCheckerTimer = new TimerEx(lineChecker, 5000, null);
            isEnd = false;
        }

        public FileSendTask(string taskName, string srcPath, TransferType transferType, IPEndPoint remotePoint,string folderName):this()
        {
            var s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            s.Connect(remotePoint);

            this.tcpClient = new TcpClientEx(s, true);

            this.tcpClient.DataReceive += tcpClient_DataReceive;

            this.TaskName = taskName;
            this.SrcPath = srcPath;
            this.Type = transferType;
            this.TargetFolderName = folderName;
        }

        public FileSendTask(string taskName, string srcPath, TransferType transferType, Socket srcSocket, string folderName, bool isOwner):this()
        {
            this.tcpClient = new TcpClientEx(srcSocket, isOwner);

            this.tcpClient.DataReceive += tcpClient_DataReceive;

            this.SrcPath = srcPath;
            this.Type = transferType;
            this.TaskName = taskName;
            this.TargetFolderName = folderName;
            RemoteIP = (IPEndPoint)srcSocket.RemoteEndPoint;

        }

        public IPEndPoint RemoteIP { private set; get; }
        public FileTransfer_FileInfo[] FileList;
        public string TaskName { private set; get; }
        public string TargetFolderName { private set; get; }

        public long FileTotalLength { private set; get; }
        public bool IsCancel { set; get; }
        public TransferType Type { set; get; }

        public string SrcPath { private set; get; }

        /// <summary>
        ///     当发送文件的请求接收到应答之后，引发该事件
        /// </summary>
        public EventHandler<FileTransfer_RespnseEventArgs> TransferRequestResponse;

        /// <summary>
        ///     单个文件传输完成时，引发的事件
        /// </summary>
        public EventHandler<FileTransfer_FileCompletedEventArgs> TransferFileCompleted;

        /// <summary>
        ///     传输结束时引发的事件
        /// </summary>
        public EventHandler<FileTransfer_AllCompletedEventArgs> TransferAllCompleted;

        public EventHandler<FileTransfer_BaseEventArgs> TransferCancel;

        private EventHandler<FileTransfer_BaseEventArgs> TransferActivity;

        public void Send()
        {
            if (SrcPath.IsNullOrEmpty())
            {
                throw new ArgumentOutOfRangeException(@"SrcPath");
            }
            switch (Type)
            {
                case TransferType.Directory:
                    if (!Directory.Exists(SrcPath))
                    {
                        throw new Exception("指定路径的文件夹不存在");
                    }

                    var fl = FileManager.GetFileList(SrcPath, true);

                    if (fl.Length <= 0)
                    {
                        throw new Exception("指定路径的文件夹为空文件夹");
                    }

                    var fileList = new List<FileSendTaskInfo>(fl.Length);

                    var parentDirectory = Path.GetDirectoryName(this.SrcPath);

                    if (parentDirectory.IsNullOrEmpty())
                    {
                        throw new Exception("指定路径错误");
                    }

                    var directoryName = this.SrcPath.Replace(parentDirectory, "");

                    foreach (var fileInfo in fl)
                    {
                        var temp = new FileSendTaskInfo();

                        temp.LockerObj = new object();
                        temp.FileID = RandomEx.Next();
                        temp.FileInfo = fileInfo;
                        //temp.FileName = directoryName + fileInfo.FullName.Replace(this.SrcPath, "");
                        temp.FileName = fileInfo.FullName.Replace(this.SrcPath, "").Trim('\\');
                        temp.FileSize = (int)fileInfo.Length;
                        temp.TargetStream = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
                        FileTotalLength += fileInfo.Length;

                        fileList.Add(temp);
                    }

                    this.FileList = fileList.ToArray();

                    break;
                case TransferType.File:
                    if (!File.Exists(SrcPath))
                    {
                        throw new Exception("指定路径的文件不存在");
                    }

                    var fi = new FileInfo(SrcPath);

                    var temp1 = new FileSendTaskInfo();
                    temp1.LockerObj = new object();
                    temp1.FileID = RandomEx.Next();
                    temp1.FileName = fi.Name;
                    temp1.FileSize = (int)fi.Length;
                    temp1.FileInfo = fi;
                    temp1.TargetStream = fi.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
                    this.FileList = new FileTransfer_FileInfo[] { temp1 };

                    FileTotalLength = temp1.FileSize;
                    break;
            }

            MainSessionID = RandomEx.Next();
            IsEnabledTransfer = false;
            IsCancel = false;

            SendFile();
        }

        public void Close()
        {
            
        }

        public void Cancel()
        {
            this.IsCancel = true;

            var cancelStruct = new FileTransfer_TransferCancelRequest();
            cancelStruct.MainSessionID = this.MainSessionID;

             SendData(cancelStruct.ToBytes());

            //OnTransferEnd(new FileTransferException_Cancel());
        }

        public static void SendFile(string srcFilePath, string descUri)
        {
            if (!File.Exists(srcFilePath))
            {
                throw new Exception("文件不存在");
            }

            if (descUri.IsNullOrEmpty())
            {
                throw new Exception("");
            }

            var remoteUri = descUri.ToUri(null);

            if (remoteUri == null)
            {
                throw new Exception();
            }

            var ipaddress = remoteUri.GetHostIPAddress();

            if (ipaddress == null)
            {
                throw new Exception();
            }

            var query = remoteUri.GetQueryInfo();

            SendFile(srcFilePath, new IPEndPoint(ipaddress, remoteUri.Port), query.TryGetValue("FolderName", ""));
        }

        public static void SendFile(string srcFilePath, IPEndPoint remotePoint, string folderName)
        {
            if (!File.Exists(srcFilePath))
            {
                throw new Exception("文件不存在");
            }

            var fileSender = new FileSendTask("sss", srcFilePath, TransferType.File, remotePoint, folderName);

            var au = new AutoResetEvent(false);
            Exception error = null;
            fileSender.TransferAllCompleted += (s, e) =>
                                               {
                                                   error = e.Error;
                                                   au.Set();
                                               };

            fileSender.Send();

            au.WaitOne();

            if (error!=null)
            {
                throw error;
            }
        }

        public static void SendDirectory(string srcDirectoryPath, string descUri)
        {
            if (!Directory.Exists(srcDirectoryPath))
            {
                throw new Exception("文件不存在");
            }

            if (descUri.IsNullOrEmpty())
            {
                throw new Exception("");
            }

            var remoteUri = descUri.ToUri(null);

            if (remoteUri == null)
            {
                throw new Exception();
            }

            var ipaddress = remoteUri.GetHostIPAddress();

            if (ipaddress == null)
            {
                throw new Exception();
            }

            var query = remoteUri.GetQueryInfo();

            //var folderName=query.TryGetValue("FolderName", "");

            //folderName=HttpUtility.UrlDecode(folderName, Encoding.UTF8);

            SendDirectory(srcDirectoryPath, new IPEndPoint(ipaddress, remoteUri.Port==-1?19863:remoteUri.Port), query.TryGetValue("FolderName", ""));
        }

        public static void SendDirectory(string srcDirectoryPath, IPEndPoint remotePoint, string folderName)
        {
            if (!Directory.Exists(srcDirectoryPath))
            {
                throw new Exception("文件夹不存在");
            }

            var fileSender = new FileSendTask("sss", srcDirectoryPath, TransferType.Directory, remotePoint, folderName);

            var au = new AutoResetEvent(false);

            fileSender.TransferAllCompleted += (s, e) =>
            {
                au.Set();
            };

            fileSender.Send();

            au.WaitOne();
        }


        private void SendFile()
        {
            Exception error = null;

            try
            {
                var transReq = new FileTransfer_TransferRequest();

                transReq.TransferType = this.Type;
                transReq.TotalFileLength = (int)this.FileTotalLength;
                transReq.FileCount = this.FileList.Length;
                transReq.TaskName = this.TaskName;
                transReq.TargetFolderName = TargetFolderName;

                transReq.FileInfos = this.FileList;

                transReq.MainSessionID = RandomEx.Next();

                this.MainSessionID = transReq.MainSessionID;

                SendData(transReq.ToBytes());

                //tcpClient.Send();

            }
            catch (Exception ex)
            {
                error = ex;
                throw;
            }
            finally
            {

            }
        }

        private void OnTransferResponse(FileTransfer_TransferResponse response)
        {
            if (response == null || response.MainSessionID != this.MainSessionID)
            {
                return;
            }



            var t = new FileTransfer_TransferBegin();
            t.MainSessionID = this.MainSessionID;
            t.TransferType = Type;
            this.IsEnabledTransfer = true;
            try
            {
                SendData(t.ToBytes());
            }
            catch (Exception)
            {
                
            }

            Events.EventHelper.RaiseIgoneError(TransferRequestResponse, this, new FileTransfer_RespnseEventArgs(this.MainSessionID, this.TaskName,response.IsEnableTransfer));
            //srcTcpClient.Send();
        }

        private void OnTransferBlockGet(FileTransfer_DataBlockRequest getRequest)
        {
            if (getRequest == null || getRequest.MainSessionID != this.MainSessionID || !this.IsEnabledTransfer)
            {
                return;
            }

            var fileConfig = (FileSendTaskInfo)this.FileList.FindItem((s) => s.FileID == getRequest.FileID);
            if (fileConfig == null)
            {
                return;
            }

            var buffer = GencelBuffer.Buffer.TakeBuffer(getRequest.GetCount);
            var readCount = 0;
            lock (fileConfig.LockerObj)
            {
                if (fileConfig.TargetStream == null)
                {
                    fileConfig.TargetStream = fileConfig.FileInfo.Open(FileMode.Open, FileAccess.Read);
                }
                else
                {
                    if (!fileConfig.TargetStream.CanRead)
                    {
                        return;
                    }
                }

                fileConfig.TargetStream.Position = getRequest.GetOffset;

                var getCount = Math.Min(getRequest.GetCount, fileConfig.TargetStream.Length - getRequest.GetOffset);

                if (getCount!=getRequest.GetCount)
                {
                    
                }

                readCount = fileConfig.TargetStream.Read(buffer, 0, (int)getCount);
            }

            var response = new FileTransfer_DataBlockResponse();
            response.MainSessionID = this.MainSessionID;
            response.Data = new ArraySegment<byte>(buffer, 0, readCount);
            response.FileID = getRequest.FileID;
            response.Offset = getRequest.GetOffset;

            try
            {
                SendData(response.ToBytes());
                //srcTcpClient.Send();
                GencelBuffer.Buffer.ReturnBuffer(buffer);
            }
            catch (Exception)
            {

            }

        }

        private void OnTransferFileCompleted(FileTransfer_SingleFileCompletedRequest request)
        {
            if (request == null || request.MainSessionID != this.MainSessionID)
            {
                return;
            }

            var fileConfig = (FileSendTaskInfo)this.FileList.FindItem((s) => s.FileID == request.FileID);
            if (fileConfig == null)
            {
                return;
            }

            lock (fileConfig.LockerObj)
            {
                fileConfig.TargetStream.Close();
                fileConfig.TargetStream.Dispose();
            }


            var response = new FileTransfer_SingleFileCompletedResponse();

            response.FileID = fileConfig.FileID;
            response.MainSessionID = this.MainSessionID;

            try
            {
                SendData(response.ToBytes());
            }
            catch (Exception)
            {
                
            }

            Events.EventHelper.RaiseIgoneError(TransferFileCompleted, this, new FileTransfer_FileCompletedEventArgs(this.MainSessionID, this.TaskName, fileConfig.FileName));

        }

        private void OnTransferAllCompleted(FileTransfer_AllFileCompletedRequest request)
        {
            if (request == null || request.MainSessionID != this.MainSessionID)
            {
                return;
            }

            var resposne = new FileTransfer_AllFileCompletedResponse();

            resposne.MainSessionID = this.MainSessionID;

            try
            {
                SendData(resposne.ToBytes());
            }
            catch (Exception)
            {
                
            }
            finally
            {
                OnTransferEnd(null);
                
            }



            //Events.EventHelper.RaiseIgoneError(TransferAllCompleted, this, new TransferAllCompletedEventArgs(this.MainSessionID, this.TaskName));
        }

        private void OnTransferCancelRequest(FileTransfer_TransferCancelRequest request)
        {
            if (request == null || request.MainSessionID != this.MainSessionID)
            {
                return;
            }

            this.IsCancel = true;

            //lock (FileList)
            //{
            //    foreach (FileSendTaskInfo info in FileList)
            //    {
            //        if (info != null)
            //        {
            //            lock (info.LockerObj)
            //            {
            //                info.TargetStream.Close();
            //                info.TargetStream.Dispose();
            //            }
            //        }
            //    }
            //}

            var resposne = new FileTransfer_TransferCancelResponse();

            resposne.MainSessionID = this.MainSessionID;

            try
            {
                SendData(resposne.ToBytes());
            }
            catch (Exception)
            {
                
            }
            finally
            {
                //tcpClient.Close();
            }

            //OnTransferEnd(new FileTransferException_Cancel());

            //Events.EventHelper.RaiseIgoneError(TransferCancel, this, new TransferBaseEventArgs(this.MainSessionID, this.TaskName));
        }

        private void OnTransferCancelResponse(FileTransfer_TransferCancelResponse response)
        {
            if (response == null || response.MainSessionID != this.MainSessionID)
            {
                return;
            }

            this.IsCancel = true;

            //lock (FileList)
            //{
            //    foreach (FileSendTaskInfo info in FileList)
            //    {
            //        if (info != null)
            //        {
            //            lock (info.LockerObj)
            //            {
            //                info.TargetStream.Close();
            //                info.TargetStream.Dispose();
            //            }
            //        }
            //    }
            //}

            //var resposne = new FileTransfer_TransferCancelResponse();

            //resposne.MainSessionID = this.MainSessionID;

            //try
            //{
            //    SendData(resposne.ToBytes());
            //}
            //catch (Exception)
            //{

            //}
            //finally
            //{
            //    //tcpClient.Close();
            //}

            OnTransferEnd(new FileTransferException_Cancel());

            //Events.EventHelper.RaiseIgoneError(TransferCancel, this, new TransferBaseEventArgs(this.MainSessionID, this.TaskName));
        }

        private void OnTransferActivityRequest(FileTransfer_TransferActivityRequest request)
        {
            if (request.MainSessionID!=this.MainSessionID)
            {
                return;
            }

            var temp = new FileTransfer_TransferActivityResponse();

            temp.MainSessionID = this.MainSessionID;

            SendData(temp.ToBytes());
        }

        private void OnTransferActivityResponse(FileTransfer_TransferActivityResponse response)
        {
            if (response.MainSessionID != this.MainSessionID)
            {
                return;
            }

            Events.EventHelper.RaiseAsync(TransferActivity,this,new FileTransfer_BaseEventArgs(this.MainSessionID,this.TaskName));

        }

        private void OnTransferEnd(Exception error)
        {
            isEnd = true;

            lock (FileList)
            {
                foreach (FileSendTaskInfo info in FileList)
                {
                    if (info != null)
                    {
                        lock (info.LockerObj)
                        {
                            info.TargetStream.Close();
                            info.TargetStream.Dispose();
                        }
                    }
                }
            }

            lineCheckerTimer.Stop();
            tcpClient.Close();


            Events.EventHelper.RaiseIgoneError(TransferAllCompleted, this, new FileTransfer_AllCompletedEventArgs(MainSessionID, TaskName, error));


        }

        private void tcpClient_DataReceive(object sender, DataReceiveEventArgs e)
        {
            if (this.IsCancel || isEnd)
            {
                return;
            }

            var cmdID = (CmdID)BitConverter.ToInt32(e.Data.Array, e.Data.Offset);

            try
            {
                switch (cmdID)
                {
                    case CmdID.FileTransfer_TransferResponse:
                        OnTransferResponse(FileTransfer_TransferResponse.Load(e.Data.Array, e.Data.Offset, e.Data.Count));
                        break;
                    case CmdID.FileTransfer_DataBlockRequest:
                        if (this.IsEnabledTransfer)
                        {
                            OnTransferBlockGet(FileTransfer_DataBlockRequest.Load(e.Data.Array, e.Data.Offset, e.Data.Count));
                        }
                        break;

                    case CmdID.FileTransfer_SingleFileCompletedRequest:
                        OnTransferFileCompleted(FileTransfer_SingleFileCompletedRequest.Load(e.Data.Array, e.Data.Offset, e.Data.Count));
                        break;
                    case CmdID.FileTransfer_TransferCancelRequest:
                        OnTransferCancelRequest(FileTransfer_TransferCancelRequest.Load(e.Data.Array, e.Data.Offset, e.Data.Count));
                        break;
                    case CmdID.FileTransfer_TransferCancelResponse:
                        OnTransferCancelResponse(FileTransfer_TransferCancelResponse.Load(e.Data.Array, e.Data.Offset, e.Data.Count));
                        break;
                    case CmdID.FileTransfer_AllFileCompletedRequest:
                        OnTransferAllCompleted(FileTransfer_AllFileCompletedRequest.Load(e.Data.Array, e.Data.Offset, e.Data.Count));
                        break;
                    case CmdID.FileTransfer_TransferActivityRequest:
                        OnTransferActivityRequest(FileTransfer_TransferActivityRequest.Load(e.Data.Array, e.Data.Offset,
                                                                                            e.Data.Count));
                        break;
                    case CmdID.FileTransfer_TransferActivityResponse:
                        OnTransferActivityResponse(FileTransfer_TransferActivityResponse.Load(e.Data.Array, e.Data.Offset,
                                                                                            e.Data.Count));
                        break;

                }
            }
            catch (Exception)
            {
                
            }



        }

        private void SendData(ArraySegment<byte> data)
        {
            if(isEnd)
            {
                return;
            }

            try
            {
                tcpClient.Send(data);
            }
            catch (Exception ex)
            {
                OnTransferEnd(ex);
                //tcpClient.Close();
                //lineCheckerTimer.Stop();
                //Events.EventHelper.Raise(TransferAllCompleted,this,new TransferAllCompletedEventArgs(MainSessionID,TaskName,ex));
            }
        }

        private void lineChecker(object state)
        {

            var au = new AutoResetEvent(false);
            var isSuccess = false;

            EventHandler<FileTransfer_BaseEventArgs> handler = (s, e) =>
                                                              {
                                                                  if (e.MainSessionID==this.MainSessionID)
                                                                  {
                                                                      isSuccess = true;
                                                                      au.Set();
                                                                  }
                                                                  
                                                              };

            this.TransferActivity += handler;

            var temp = new FileTransfer_TransferActivityRequest();

            temp.MainSessionID = this.MainSessionID;

            SendData(temp.ToBytes());

            au.WaitOne(10000);

            this.TransferActivity -= handler;

            if (!isSuccess)
            {
                Interlocked.Increment(ref offlineCount);

                var tempCount = offlineCount;

                if (tempCount>5)
                {
                    OnTransferEnd(new FileTransferException_Offline());
                }
            }
            else
            {
                Interlocked.Exchange(ref offlineCount, 0);
            }

        }

    }

    [Serializable]
    public class FileTransfer_BaseEventArgs : EventArgs
    {
        public FileTransfer_BaseEventArgs(int mainSessionID, string taskName)
        {
            MainSessionID = mainSessionID;
            TaskName = taskName;
        }

        public int MainSessionID { get; private set; }

        public string TaskName { get; private set; }
    }

    [Serializable]
    public class FileTransfer_AllCompletedEventArgs : FileTransfer_BaseEventArgs
    {
        public FileTransfer_AllCompletedEventArgs(int mainSessionID, string taskName,Exception error) : base(mainSessionID, taskName)
        {
            Error = error;
        }

        public FileTransfer_AllCompletedEventArgs(int mainSessionID, string taskName)
            : this(mainSessionID, taskName,null)
           
        {
        }

        public bool HasError
        {
            get { return Error != null; }
        
        }

        public Exception Error { get; private set; }
    }

    [Serializable]
    public class FileTransfer_FileCompletedEventArgs : FileTransfer_BaseEventArgs
    {
        public FileTransfer_FileCompletedEventArgs(int mainSessionID, string taskName,string fileName,Exception error)
            : base(mainSessionID, taskName)
        {
            Error = error;
        }

        public FileTransfer_FileCompletedEventArgs(int mainSessionID, string taskName,string fileName)
            : this(mainSessionID, taskName, fileName,null)
        {
            FileName = fileName;
        }

        public string FileName { get; private set; }

        public bool HasError { get { return Error != null; } }

        public Exception Error { get; private set; }
    }

    [Serializable]
    public class FileTransfer_RespnseEventArgs : EventArgs
    {
        public FileTransfer_RespnseEventArgs(int mainSessionID, string taskName,bool isEnabled)
        {
            MainSessionID = mainSessionID;
            TaskName = taskName;
            IsEnabled = isEnabled;
        }

        public int MainSessionID { get; private set; }

        public string TaskName { get; private set; }

        public bool IsEnabled { get; private set; }
    }

    [Serializable]
    public class FileSendTaskInfo : FileTransfer_FileInfo
    {
        [NonSerialized]
        public Stream TargetStream;

        [NonSerialized]
        public object LockerObj;

        [NonSerialized]
        public FileInfo FileInfo;
    }

    public class FileSendCompletedEventArgs : EventArgs
    {
        public string TaskName;
        public FileInfo FileInfo;
        public long TotalFileLength;
        public long TotalSendLength;
    }
}
